<template>
  <div
    class="relative"
    ref="containerTarget"
    :style="{
      height: containerHeight + 'px',
    }"
  >
    <!-- 数据层 -->
    <!-- columnWidth 有宽度在显示 -->
    <template v-if="columnWidth && data.length">
      <div
        v-for="(item, index) in data"
        :key="nodeKey ? item[nodeKey] : index"
        class="m-waterfall-item absolute duration-300"
        :style="{
          width: columnWidth + 'px',
          left: item._style?.left + 'px',
          top: item._style?.top + 'px',
        }"
      >
        <slot :item="item" :width="columnWidth" :index="index"></slot>
      </div>
    </template>
  </div>
</template>
<script>
export default {
  name: "waterfall",
};
</script>
<script setup>
import {
  computed,
  nextTick,
  onMounted,
  onUnmounted,
  ref,
  watch,
} from "vue-demi";
import {
  getImgElements,
  getAllImg,
  onComplateImgs,
  getMinHeightColumn,
  getMinHeight,
  getMaxHeight,
} from "./utils";
const props = defineProps({
  // 数据源
  data: {
    type: Array,
    default: () => [],
    required: true,
  },
  //   唯一标识key (id)
  nodeKey: {
    type: String,
  },
  //   列数
  column: {
    default: 2,
    type: Number,
  },
  //   列间距
  columnSpacing: {
    default: 20,
    type: Number,
  },
  //   列间距
  rowSpacing: {
    default: 20,
    type: Number,
  },
  picturePreReading: {
    type: Boolean,
    default: true,
  },
});

// 容器的高度 = 每一列最高的一个的集合
const containerHeight = ref(0);
// 记录每列高度的 容器
const columnHeightObj = ref({});
// 构建记录每一列高度的对象
const useColumnHeightObj = () => {
  columnHeightObj.value = {};
  for (let i = 0; i < props.column; i++) {
    columnHeightObj.value[i] = 0;
  }
};
// 容器的ref
const containerTarget = ref(null);
// 容器的左边距,计算item的 left
const containerLeft = ref(0);
// 容器的宽度
const containerWdith = ref(0);
// 计算容器的总宽度(不包含 padding margin border)
const useContainerWidth = () => {
  // getComputedStyle原生方法,参数1 dom对象 返回当前dom的style
  const { paddingLeft, paddingRight } = getComputedStyle(
    containerTarget.value,
    null
  );
  //   容器的左边距
  containerLeft.value = parseFloat(paddingLeft);
  //   r容器的宽度
  containerWdith.value =
    containerTarget.value.offsetWidth -
    parseFloat(paddingLeft) -
    parseFloat(paddingRight);
};
// 列宽 = 容器的宽度 - 所有的列间距宽度 在除以列数
const columnWidth = ref(0);
// 列间距的合计
const columnSpacingTotal = computed(() => {
  return (props.column - 1) * props.columnSpacing;
});
// 结算列宽
const useColumnWidth = () => {
  // 计算容器宽度
  useContainerWidth();
  columnWidth.value =
    (containerWdith.value - columnSpacingTotal.value) / props.column;
};
onMounted(() => {
  useColumnWidth();
});
// item的高度集合
let itemHeights = [];
// 监听图片加载完毕
const awaitImgComplate = () => {
  itemHeights = [];
  // 先拿到所有的元素
  let itemElement = [...document.getElementsByClassName("m-waterfall-item")];

  // 获取到 元素中的img标签
  const imgElements = getImgElements(itemElement);
  // 获取所有的img标签的图片
  const allImg = getAllImg(imgElements);
  // 等待图片加载完成
  onComplateImgs(allImg).then((res) => {
    // 此时图片加载完毕 在获取item的高度

    itemElement.forEach((item) => {
      itemHeights.push(item.offsetHeight);
    });
    // 渲染item位置
    useItemLocation();
  });
};
// 不需要图片预加载 - 不会计算图片高度,需要在外部自行设置图片宽高
const useItemHeight = () => {
  itemHeights = [];
  // 先拿到所有的元素
  let itemElement = [...document.getElementsByClassName("m-waterfall-item")];
  // 获取到 元素中的img标签
  itemElement.forEach((item) => {
    itemHeights.push(item.offsetHeight);
  });
  // 渲染item位置
  useItemLocation();
};
// 渲染item位置
const useItemLocation = () => {
  props.data.forEach((item, index) => {
    if (item._style) {
      return;
    }
    item._style = {};
    item._style.left = getItemLeft();
    item._style.top = getItemTop();
    increasingHeight(index);
  });
  // console.log(props.data, "itemHeights");
  // 所有的item计算完毕之后 在计算容器的高度
  containerHeight.value = getMaxHeight(columnHeightObj.value);
};
// 返回下一个 item 的 left
const getItemLeft = () => {
  // 拿到最小宽度的列
  const column = getMinHeightColumn(columnHeightObj.value);
  return (
    column * (columnWidth.value + props.columnSpacing) + containerLeft.value
  );
};
// 返回下一个 item 的 top
const getItemTop = () => {
  return getMinHeight(columnHeightObj.value);
};
// 指定列的高度自增
const increasingHeight = (index) => {
  // 最小高度所在的列
  const minHeightColumn = getMinHeightColumn(columnHeightObj.value);
  columnHeightObj.value[minHeightColumn] +=
    itemHeights[index] + props.rowSpacing;
};
watch(
  () => props.data,
  (newValue) => {
    nextTick(() => {
      // 第一次获取数据构建高度
      const resColumn = newValue.every((item) => !item._style);
      if (resColumn) {
        useColumnHeightObj();
      }
      if (props.picturePreReading) {
        awaitImgComplate();
      } else {
        useItemHeight();
      }
      // setTimeout(() => {
      //   if (props.picturePreReading) {
      //     awaitImgComplate();
      //   } else {
      //     useItemHeight();
      //   }
      // }, 300);
    });
  },
  {
    deep: true,
    immediate: true,
  }
);
const reset = () => {
  setTimeout(() => {
    useColumnWidth();
    props.data.forEach((item) => {
      item._style = null;
    });
  }, 100);
};
watch(
  () => props.column,
  () => {
    if (props.picturePreReading) {
      columnWidth.value = 0;
      reset();
    } else {
      reset();
    }
  }
);
onUnmounted(() => {
  props.data.forEach((item) => {
    delete item._style;
  });
});
</script>

<style lang="scss" scoped>
</style>